home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.1 (Developer) [x86] / NeXT Step 3.1 Intel dev.cdr.dmg / NextDeveloper / Examples / AppKit / Graph / LineGraph.m < prev    next >
Text File  |  1992-06-21  |  8KB  |  280 lines

  1.  
  2. /*
  3.     LineGraph.m
  4.  
  5.     LineGraph is a simple view which plots a set of points as a connected line.
  6.     LineGraph draws the graph using a userpath.  It retains the two arrays
  7.     that make up the userpath: the coordinates of the userpath and the
  8.     userpath operators (e.g., dps_lineto).
  9.  
  10.     You may freely copy, distribute, and reuse the code in this example.
  11.     NeXT disclaims any warranty of any kind, expressed or implied, as to its
  12.     fitness for any particular use.
  13. */
  14.  
  15. #import "Graph.h"
  16.  
  17. static void drawAxes(float x1, float y1, float x2, float y2);
  18. static void testBounds(float *min, float *max, float *points, int num);
  19.  
  20. #define POS_INFINITY    (1.0/0.0)
  21. #define NEG_INFINITY    (-1.0/0.0)
  22. #define IS_NAN(x)    ((x)!=(x))    /* IEEE test for NAN (not a number) */
  23. #define IS_STRANGE(x)    (IS_NAN(x) || x == POS_INFINITY || x == NEG_INFINITY)
  24.  
  25.  
  26. @implementation LineGraph
  27.  
  28. - initFrame:(NXRect *)aRect {
  29.     [super initFrame:aRect];
  30.     lineGray = NX_WHITE;
  31.     backgroundGray = NX_BLACK;
  32.     return self;
  33. }
  34.  
  35. - free {
  36.     NXZoneFree([self zone], points);
  37.     NXZoneFree([self zone], ops);
  38.     return [super free];
  39. }
  40.  
  41. - setPoints:(int)num x:(float *)x y:(float *)y
  42.     minX:(float)minX minY:(float)minY maxX:(float)maxX maxY:(float)maxY {
  43.     float *f;
  44.     int i;
  45.     char *op;
  46.     NXZone *zone;
  47.     int segPoints;
  48.  
  49.  /*
  50.   * We copy these points into our own an array that we later use when
  51.   * we draw them as a userpath.  This array has the x and y values
  52.   * interleaved.  We also generate an array of userpath operators that we
  53.   * will use to draw the userpath.  These operators are just a moveto to
  54.   * get to the start of the line, and then lineto's connecting all the points
  55.   * from there on.
  56.   *
  57.   * We also filter out the troublesome floating point values of positive
  58.   * infinity, negative infinity and NAN ("not a number").  When we encounter
  59.   * these values, we skip that particular point and remember to start a new
  60.   * segment of the graph when we see the next well behaved value.
  61.   */
  62.     zone = [self zone];
  63.     NXZoneFree(zone, points);
  64.     NXZoneFree(zone, ops);
  65.     numPoints = num;
  66.     points = NXZoneMalloc(zone, 2 * num * sizeof(float));
  67.     ops = NXZoneMalloc(zone, num * sizeof(char));
  68.     numPoints = 0;
  69.     segPoints = 0;
  70.     for (f = points, op = ops, i = num; i--; ) {
  71.     if (IS_STRANGE(*x) || IS_STRANGE(*y)) {
  72.         if (segPoints == 1) {
  73.           /*
  74.            * This is a the case of a well behaved point with weird values
  75.            * on either side.  In this case we just make a tiny line
  76.            * segment of this point to itself to.
  77.            */
  78.         *f++ = f[-2];
  79.         *f++ = f[-2];
  80.         *op++ = dps_lineto;
  81.         numPoints++;
  82.         }
  83.         segPoints = 0;
  84.         x++;
  85.         y++;
  86.     } else {
  87.         *f++ = *x++;
  88.         *f++ = *y++;
  89.         if (segPoints == 0)
  90.         *op++ = dps_moveto;
  91.         else
  92.         *op++ = dps_lineto;
  93.         segPoints++;
  94.         numPoints++;
  95.     }
  96.     }
  97.     if (numPoints > 0) {
  98.     testBounds(&minX, &maxX, points, numPoints);
  99.     bbox[0] = minX;
  100.     bbox[2] = maxX;
  101.     testBounds(&minY, &maxY, points+1, numPoints);
  102.     bbox[1] = minY;
  103.     bbox[3] = maxY;
  104.     } else {
  105.     bbox[0] = bbox[1] = -1;
  106.     bbox[2] = bbox[3] = 1;
  107.     }
  108.     return self;
  109. }
  110.  
  111. - sizeTo:(NXCoord)width :(NXCoord)height {
  112.     NXPoint center;
  113.  
  114.  /*
  115.   * We override sizeto so that after we're resized the point at the center
  116.   * of the view remains the same.  We just note the center, pass the message
  117.   * up to the super class, and then reset the center to what it was.
  118.   */
  119.     center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2;
  120.     center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2;
  121.     [super sizeTo:width :height];
  122.     [self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2
  123.             :center.y - NX_HEIGHT(&bounds) / 2];
  124.     return self;
  125. }
  126.  
  127. - scaleToFit {
  128.     float scale;
  129.     float bbWidth = bbox[2] - bbox[0];
  130.     float bbHeight = bbox[3] - bbox[1];
  131.  
  132.  /*
  133.   * First we figure out how much we need to scale so that our current graph
  134.   * will just fit within the view.  We do this by taking the ratio of the
  135.   * the view's current size to the bounding box of the graph.  We also add
  136.   * a little fudge factor of 0.95 so the graph will have a little border
  137.   * space around it.
  138.   */
  139.     if (bbWidth == 0 && bbHeight == 0)
  140.     scale = 10;
  141.     else if (bbWidth == 0)
  142.     scale = NX_HEIGHT(&frame) / bbHeight * 0.95;
  143.     else if (bbHeight == 0)
  144.     scale = NX_WIDTH(&frame) / bbWidth * 0.95;
  145.     else
  146.     scale = MIN(NX_WIDTH(&frame) / bbWidth,
  147.             NX_HEIGHT(&frame) / bbHeight) * 0.95;
  148.  
  149.   /* scale the size of the view */
  150.     [self setDrawSize:NX_WIDTH(&frame) / scale :NX_HEIGHT(&frame) / scale];
  151.  
  152.   /*
  153.    * translate the view so the graph's bounding box is in the lower left corner
  154.    */
  155.     [self setDrawOrigin:bbox[0] - (NX_WIDTH(&bounds) - bbWidth) / 2
  156.             :bbox[1] - (NX_HEIGHT(&bounds) - bbHeight) / 2];
  157.     return self;
  158. }
  159.  
  160. - zoom:(float)scale {
  161.     NXPoint center;
  162.  
  163.   /* remember the center to we can reset it after the scaling */
  164.     center.x = NX_X(&bounds) + NX_WIDTH(&bounds) / 2;
  165.     center.y = NX_Y(&bounds) + NX_HEIGHT(&bounds) / 2;
  166.  
  167.   /* scale the view to its new size */
  168.     [self setDrawSize:NX_WIDTH(&bounds) / scale :NX_HEIGHT(&bounds) / scale];
  169.  
  170.   /* reset the center point */
  171.     [self setDrawOrigin:center.x - NX_WIDTH(&bounds) / 2
  172.             :center.y - NX_HEIGHT(&bounds) / 2];
  173.     return self;
  174. }
  175.  
  176. /*
  177.  * In both the drawSelf method and the drawAxes function, we use a little
  178.  * trick to get the right line width regardless of how much we are zoomed
  179.  * in.  If we are drawing on the screen, we just draw the lines with zero
  180.  * width.  This the fastest way to draw lines, and ensures they will always
  181.  * one pixel wide.  When drawing on the printer, we do the trick of
  182.  * setting the matrix to the default matrix after the path has been made
  183.  * but before it is stroked.  Since the line width is actually used when
  184.  * the path is stroked, this makes the line width be the same regardless of
  185.  * what scale we were at when we stroked the path.  The purpose of this is
  186.  * to make the line width independent of how far in or our we are zoomed.
  187.  */
  188.  
  189. - drawSelf:(const NXRect *)rects :(int)rectCount {
  190.     PSsetgray(backgroundGray);
  191.     NXRectFill(&bounds);
  192.     drawAxes(NX_X(&bounds), NX_Y(&bounds), NX_MAXX(&bounds), NX_MAXY(&bounds));
  193.     if (points && numPoints > 0) {
  194.     PSnewpath();
  195.     PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 1.0);
  196.     PSsetgray(lineGray);
  197.     if (NXDrawingStatus == NX_DRAWING)
  198.       /* stroke the userpath */
  199.         DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints,
  200.                             bbox, dps_ustroke);
  201.     else {
  202.       /* append the userpath to the current path, but dont stroke yet */
  203.         DPSDoUserPath(points, numPoints * 2, dps_float, ops, numPoints,
  204.                             bbox, dps_uappend);
  205.       /* trick to ensure line width is independent of zoom */
  206.         PSgsave();
  207.         PSmatrix();
  208.         PSdefaultmatrix();
  209.         PSsetmatrix();
  210.         PSstroke();
  211.         PSgrestore();
  212.     }
  213.     }
  214.     return self;
  215. }
  216.  
  217.  
  218. /*
  219.  * Simple line drawer, used to draw the axes of the graph in the current
  220.  * color and line width.
  221.  */
  222. static void drawAxes(float x1, float y1, float x2, float y2) {
  223.     PSsetlinewidth(NXDrawingStatus == NX_DRAWING ? 0.0 : 0.4);
  224.     PSsetgray(NX_DKGRAY);
  225.     PSnewpath();
  226.     PSmoveto(0.0, y1);
  227.     PSlineto(0.0, y2);
  228.     PSmoveto(x1, 0.0);
  229.     PSlineto(x2, 0.0);
  230.     if (NXDrawingStatus != NX_DRAWING) {
  231.       /* trick to ensure line width is independent of zoom */
  232.     PSgsave();
  233.     PSmatrix();
  234.     PSdefaultmatrix();
  235.     PSsetmatrix();
  236.     PSstroke();
  237.     PSgrestore();
  238.     } else
  239.     PSstroke();
  240. }
  241.  
  242. - setLineGray:(float)gray {
  243.     lineGray = gray;
  244.     return self;
  245. }
  246.  
  247. - setBackgroundGray:(float)gray {
  248.     backgroundGray = gray;
  249.     return self;
  250. }
  251.  
  252. - (float)lineGray {
  253.     return lineGray;
  254. }
  255.  
  256. - (float)backgroundGray {
  257.     return backgroundGray;
  258. }
  259.  
  260. /*
  261.  * Tests the bounds for NAN or infinite values.  If there are such values,
  262.  * we recalc the bounds with the given points.  Since these points are
  263.  * the interleaved x-y pairs for the userpath, we skip every other value,
  264.  * since we want to only consider x's or y's.
  265.  */
  266. static void testBounds(float *min, float *max, float *points, int num) {
  267.     if (IS_STRANGE(*min) || IS_STRANGE(*max)) {
  268.     *min = *max = *points;
  269.     while (--num) {
  270.         points += 2;
  271.         if (*points > *max)
  272.         *max = *points;
  273.         else if (*points < *min)
  274.         *min = *points;
  275.     }
  276.     }
  277. }
  278.  
  279. @end
  280.